﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using Microsoft.Win32;

namespace SportManager
{
    [Serializable]
    public class DataHolder
    {
        /// <summary>
        /// ID of the item
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// Is item updated
        /// </summary>
        [NonSerialized]
        public bool m_isUpdated;

        /// <summary>
        /// Item has been updated
        /// </summary>
        public void Update()
        {
            m_isUpdated = true;
        }
    }

    /// <summary>
    /// Interface for various data keepers
    /// </summary>
    /// <typeparam name="TDataType">Stored data type</typeparam>
    public interface IDataManager<TDataType>
    {
        /// <summary>
        /// Get data from storage
        /// </summary>
        /// <returns>Readed data</returns>
        TDataType GetData();

        /// <summary>
        /// Save data to storage
        /// </summary>
        void SaveData();
    }    

    /// <summary>
    /// Interface helping to find elements in List of IdOwners collections
    /// </summary>
    /// <typeparam name="TData">Object with parent IdOwner</typeparam>
    public interface IListOfIdOwners<TData>
        where TData : DataHolder
    {
        /// <summary>
        /// Get index of searched item in list of data
        /// </summary>
        /// <param name="guid">Searched guid</param>
        /// <returns>Index of searched item</returns>
        int GetItemIndex(Guid guid);

        /// <summary>
        /// Finde item by guid in list of data
        /// </summary>
        /// <param name="guid">Searched guid</param>
        /// <returns>Founded item</returns>
        TData FindItemByGuid(Guid guid);
    }

    /// <summary>
    /// Interface for keepers, that can save data to different storages
    /// </summary>
    public interface IKeeper<TDataType>
    {
        /// <summary>
        /// Get data from storage
        /// </summary>
        /// <returns>Object getted from storage</returns>
        TDataType Get();

        /// <summary>
        /// Save data to storage
        /// </summary>
        /// <param name="obj">Object that you want to save</param>
        void Set(TDataType obj);

        /// <summary>
        /// Initialize Keeper. Must be called before Get() or Set()
        /// </summary>
        /// <param name="args">Arguments for initializing keeper (e.g. file name)</param>
        void Initialize(string[] args);
    }

    #region Keeper's realization details
    /// <summary>
    /// Bridge between keepers and their clients.
    /// </summary>
    /// <typeparam name="Keeper">Implement of IKeeper, that can save data to storate</typeparam>
    /// <typeparam name="TDataType">Type of stored data</typeparam>
    public class KeepersBridge<Keeper, TDataType>
        where Keeper : IKeeper<TDataType>
        where TDataType : new()
    {
        /// <summary>
        /// Get data from keeper. (operation may be long if it run for the first time) 
        /// </summary>
        /// <param name="args">Arguments for keeper (e.g. : file name / table name / e.t.c)</param>
        /// <returns>Requested data</returns>
        public static TDataType GetData(string[] args)
        {
            CreateKeeperIfNeed(args);
            if (s_storedData == null)
            {
                s_storedData = (TDataType)s_dataKeeper.Get();
                if (s_storedData == null)
                {
                    s_storedData = new TDataType();
                }
            }

            return s_storedData;
        }

        /// <summary>
        /// Save data to keeper
        /// </summary>
        /// <param name="args">Arguments for keeper (e.g. : file name / table name / e.t.c)</param>
        public static void SaveData(string[] args)
        {
            CreateKeeperIfNeed(args);
            s_dataKeeper.Set(s_storedData);
        }

        private static void CreateKeeperIfNeed(string[] args)
        {
            if (s_dataKeeper == null)
            {
                s_dataKeeper = (Keeper)Activator.CreateInstance(typeof(Keeper));
                s_dataKeeper.Initialize(args);
            }
        }

        private static TDataType s_storedData;
        private static Keeper s_dataKeeper;
    }

    /// <summary>
    /// Keeper, that store data to file
    /// </summary>
    public class FileKeeper<TDataType> : IKeeper<TDataType>
    {
        /// <summary>
        /// Initializing FileKeeper
        /// </summary>
        /// <param name="args">FileKeeper required one string with file name</param>
        public void Initialize(string[] args)
        {
            if (args.Length == 0)
            {
                throw new Exception("There is no required argument in the arguments array.");
            }

            m_filePath = args[0];
        }

        /// <summary>
        /// Get data from file
        /// </summary>
        /// <returns>Deserialized object, or null if file is empty or corrupted</returns>
        public TDataType Get()
        {
            BinaryFormatter formater = new BinaryFormatter();
            FileStream fileStream = new FileStream(m_filePath, FileMode.OpenOrCreate);
            object result;
            try
            {
                result = formater.Deserialize(fileStream);
            }
            catch
            {
                result = null;
            }
            finally
            {
                fileStream.Close();
            }

            return (TDataType)result;
        }

        /// <summary>
        /// Save data to file
        /// </summary>
        /// <param name="obj">Object that you want to save</param>
        public void Set(TDataType storedObject)
        {
            if (storedObject != null)
            {
                BinaryFormatter formater = new BinaryFormatter();
                FileStream fileStream = new FileStream(m_filePath, FileMode.Create);
                formater.Serialize(fileStream, storedObject);
                fileStream.Close();
            }
        }

        private string m_filePath;
    }

    /// <summary>
    /// Keeper, that store data to registry. Note, that it can store only simple data.
    /// </summary>
    /// <typeparam name="TDataType">Type of saving data.</typeparam>
    public class RegistryKeeper<TDataType> : IKeeper<TDataType>
        where TDataType : new()
    {
        /// <summary>
        /// Get data from registry.
        /// </summary>
        /// <returns></returns>
        public TDataType Get()
        {
            RegistryKey root = Registry.CurrentUser.OpenSubKey(m_RegistryPath);
            if (root == null)
            {
                object obj = null;
                return (TDataType)obj;
            }

            TDataType result = new TDataType();
            PropertyInfo[] infos = result.GetType().GetProperties();            
            foreach (PropertyInfo info in infos)
            { 
                string[] valueNames = root.GetSubKeyNames();
                object value = root.GetValue(info.Name);                
                info.SetValue(result, value, null);
            }

            root.Close();
            return result;
        }

        /// <summary>
        /// Save data to registry.
        /// </summary>
        /// <param name="storedObject">Object that you want to save.</param>
        public void Set(TDataType storedObject)
        {
            if (storedObject != null)
            {
                PropertyInfo[] infos = storedObject.GetType().GetProperties();                

                RegistryKey root = Registry.CurrentUser.OpenSubKey(m_RegistryPath, true);
                if (root == null)
                {
                    root = Registry.CurrentUser.CreateSubKey(m_RegistryPath);
                }

                foreach (PropertyInfo info in infos)
                {
                    object value = info.GetValue(storedObject, null);
                    if (value != null)
                    {
                        root.SetValue(info.Name, value.ToString());
                    }
                }

                root.Close();
            }
        }

        /// <summary>
        /// Initializing RegistryKeeper.
        /// </summary>
        /// <param name="args">RegistryKeeper required one string with registry path.</param>
        public void Initialize(string[] args)
        {
            if (args.Length == 0)
            {
                throw new Exception("There is no required argument in the arguments array.");
            }

            m_RegistryPath = args[0];
        }

        private string m_RegistryPath; 
    }
#endregion
}
